package eu.robico.simulator.simulatorinterface.utils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import eu.robico.simulator.simulatorinterface.actionpanel.Line;
import eu.robico.simulator.simulatorinterface.actionpanel.Position;

public class Boundary 
{
	private final Position[] points;
	
	public Boundary(Position[] points)
	{
		this.points = points;
	}
	
	public Boundary(List<Position> points)
	{
		this.points = points.toArray(new Position[]{});
	}

	/**
	 * Return true if the given point is contained inside the boundary.
     * See: http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
     * @param test The point to check
     * @return true if the point is inside the boundary, false otherwise
     *
     */
	public boolean contains(Position test) 
	{
		int i;
		int j;
		boolean result = false;
		for (i = 0, j = points.length - 1; i < points.length; j = i++) 
			if ((points[i].y > test.y) != (points[j].y > test.y) &&
				(test.x < (points[j].x - points[i].x) * (test.y - points[i].y) / (points[j].y-points[i].y) + points[i].x)) 
				result = !result;
		return result;
	}
	
	public List<Position> getPoints()
	{
		return Collections.unmodifiableList(Arrays.asList(points));
	}
	
	public boolean containsOne(List<Position> points)
	{
		for (Position point : points)
			if (contains(point))
				return true;
		return false;
	}
	
	public Boundary rotateAroundPosition(Position position, double rotation)
	{
		List<Position> points = new ArrayList<>();
		for (Position point : this.points)
			points.add(Toolkit.rotatePosition(point, position, rotation));
		return new Boundary(points);
	}
	
	public boolean colidesWith(Boundary bounds)
	{
		//Check if one of the corner points of either boundary is inside the other boundary
		if (	containsOne(bounds.getPoints()) || 
				bounds.containsOne(getPoints()))
			return true;
		//Check if these bounds are intersecting
		return intersectsWith(bounds);
	}
	
	public boolean intersectsWith(Boundary bounds)
	{
		List<Line> 	lines1 = Toolkit.boundaryToLines(this),
					lines2 = Toolkit.boundaryToLines(bounds);
		for (Line line1 : lines1)
			for (Line line2 : lines2)
				if (line1.intersectsWith(line2))
					return true;
		return false;
	}
	
	public static Boundary getRectangleBounds(Position center, double width)
	{
		return new Boundary(new Position[]{
				new Position(center.x - (width / 2), center.y - (width / 2)),
				new Position(center.x + (width / 2), center.y - (width / 2)),
				new Position(center.x + (width / 2), center.y + (width / 2)),
				new Position(center.x - (width / 2), center.y + (width / 2))
		});
	}
	
	public static Boundary getRectangleBounds(Position center, double width, double height)
	{
		return new Boundary(new Position[]{
				new Position(center.getX() - (width / 2), center.getY() - (height / 2)),
				new Position(center.getX() + (width / 2), center.getY() - (height / 2)),
				new Position(center.getX() + (width / 2), center.getY() + (height / 2)),
				new Position(center.getX() - (width / 2), center.getY() + (height / 2))
			});
	}
	
	public static Boundary getRotatedRectangleBounds(Position center, double rotation, double width, double height)
	{		
		Boundary rectangle = getRectangleBounds(center, width, height);
		return rectangle.rotateAroundPosition(center, rotation);
	}
	
	public String toString()
	{
		StringBuilder sb = new StringBuilder();
		sb.append("Boundary exists of following points: ");
		for (Position point : points)
			sb.append(point.toString() + " ");
		sb.append("(" + points.length + " points total)");
		return sb.toString();
	}
	
}